package com.dh.convert.node;

import java.lang.reflect.Field;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;

import com.alibaba.fastjson.util.TypeUtils;
import com.dh.convert.constants.Constants;
import com.dh.convert.constants.TypeEnum;
import com.dh.convert.exception.ConvertInitException;
import com.dh.convert.exception.ConvertValueException;
import com.dh.convert.exception.ValidateException;
import com.dh.convert.jackson.JSON;
import com.dh.convert.mapping.BeanMappingObject;
import com.dh.convert.mapping.BeanMappingValidator;
import com.dh.convert.utils.ClassUtils;
import com.dh.convert.utils.JsonNodeUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.Maps;
import com.googlecode.aviator.Expression;

public abstract class AbstractNode implements TreeNode {

	protected boolean _isContext = Boolean.FALSE;
	protected boolean _isNestIng = Boolean.FALSE;
	protected boolean _collectionMapping = Boolean.FALSE;
	protected Class<?> rootClass;
	protected Class<?> currentClass;
	protected Field f;
	protected String typeName;
	protected String nodeName;
	protected boolean _isRoot = Boolean.FALSE;
	protected boolean _isArray = Boolean.FALSE;
	protected boolean _isColltion = Boolean.FALSE;
	protected boolean _isCommon = Boolean.FALSE;
	protected boolean _isObject = Boolean.FALSE;
	protected boolean _isMap = Boolean.FALSE;
	protected boolean _isEnum = Boolean.FALSE;
	protected TreeNode parentNode;
	protected String parentNodeName;
	protected Map<String, TreeNode> childNodes = Maps.newLinkedHashMap();
	protected Map<TreeNode, BeanMappingObject> mappingChildNodes = Maps.newLinkedHashMap();

	public Map<String, TreeNode> getChildNodes() {
		return childNodes;
	}

	public boolean isContext() {
		return _isContext;
	}

	public TreeNode bulid(Field f) {
		this.f = f;
		this.f.setAccessible(Boolean.TRUE);
		this.nodeName = f.getName();
		this.currentClass = f.getType();
		this.typeName = currentClass.getSimpleName();
		return this;
	}

	public TreeNode setEntryClass(Class<?> cls) {
		return this;
	}

	public void isCollectionMapping(boolean isCollectionMapping) {
	}

	public void setSrcPath(String srcPath) {
	}

	public String getSrcPath() {
		return null;
	}

	public void setSrcListName(String srcListName) {
	}

	public String getSrcListName() {
		return null;
	}

	public boolean isNestIng() {
		return _isNestIng;
	}

	public void isNestIng(boolean _isNestIng) {
		this._isNestIng = _isNestIng;
	}

	public boolean isEnum() {
		return _isEnum;
	}

	public boolean isCollectionMapping() {
		return _collectionMapping;
	}

	/**
	 * targetObject为map时使用
	 */
	public TreeNode setChlidNode(String path, BeanMappingObject bmo) {
		try {
			Class<?> currentClass = ClassUtils.getTypeClass(bmo.getType());
			TreeNode childNode = null;
			int index = path.indexOf(Constants.POINT);
			int endIndex = (index == -1 ? path.length() : index);
			String currentName = path.substring(0, endIndex);
			TypeEnum type = TypeEnum.type(currentName);
			if (index != -1 && type == null)
				throw new IllegalArgumentException("nesting path must have flag(@,*,$)");
			String currentNodeName = null;
			if(type == null)
				currentNodeName = currentName;
			else
				currentNodeName = currentName.substring(1);
			childNode = childNodes.get(currentNodeName);
			if (childNode == null) {
				childNode = BeanNode.getNode(currentName, type, currentClass);
				childNode.setSrcPath(bmo.getSrcField());
				childNode.setSrcListName(bmo.getSrcList());
				childNode.setParent(this);
				childNodes.put(currentNodeName, childNode);
				mappingChildNodes.put(childNode, null);
			}
			if (index != -1) {
				if ("Y".equals(bmo.getCollectionMapping()))
					childNode.isCollectionMapping(Boolean.TRUE);
				if (childNode.isCollectionMapping() && childNode.getSrcPath() == null) {
					childNode.setSrcPath(bmo.getSrcField());
					childNode.setSrcListName(bmo.getSrcList());
				}
				childNode.isNestIng(Boolean.TRUE);
				childNode.setChlidNode(path.substring(index + 1), bmo);
			} else {
				mappingChildNodes.put(childNode, bmo);
				bmo.setTypeClass(currentClass);
				bmo._castDefaultValue();
			}
			return childNode;
		} catch (Exception e) {
			throw new ConvertInitException("setChlidNode error " + bmo.toString(), e);
		}
	}

	public Object writeValue(JsonNode jn, BeanMappingObject bmo, Object value) {
		if (jn != null){
			if(bmo.getTypeClass()==null)
				throw new ConvertValueException(String.format("type不能为空 %s", bmo.toString()));
			return JSON.treeToValue(jn, bmo.getTypeClass());
		}
		return value;
	}

	public Object getTargetValue(JsonNode jn, BeanMappingObject bmo, Object targetObject, Map<String, Object> context,
			Integer num) {
		try {
			/* 获取默认值 */
			Object value = bmo.getCastDefaultValue();
			JsonNode currentJsonNode = null;
			if (bmo.getFirstEl()) {
				value = elExecute(bmo, context, value);
				value = writeValue(currentJsonNode, bmo, value);
			} else if (bmo.getSrcFieldExpression() != null) {
				context.put(Constants.LIST_NUM, num);
				Object expressionValue = bmo.getSrcFieldExpression().execute(context);
				value = (expressionValue==null?value:expressionValue);
			} else {
				/* 从jsonNode根据type获取value */
				currentJsonNode = JsonNodeUtils.getJsonNode(jn, bmo.getSrcField(), num);
				value = writeValue(currentJsonNode, bmo, value);
			}
			/* validator校验 */
			validate(bmo, value);
			/* rule校验 */
			ruleExecute(bmo, context, value);
			if (!bmo.getFirstEl()) {
				/* el表达式设值 */
				value = elExecute(bmo, context, value);
			}
			return value;
		} catch (ValidateException e) {
			throw e;
		} catch (Exception e) {
			throw new ConvertValueException(bmo.toString() + " getTargetValue error", e);
		}
	}

	private void validate(BeanMappingObject bmo, Object value) {
		if (bmo.getValidators() != null) {
			for (BeanMappingValidator bv : bmo.getValidators()) {
				if (!bv.getValidator().isValid(value))
					throw new ValidateException(
							bv.getErrorMsg() == null ? bmo.getSrcField() + " value error" : bv.getErrorMsg());
			}
		}
	}

	private void ruleExecute(BeanMappingObject bmo, Map<String, Object> context, Object value) {
		Expression ruleExp = bmo.getRuleExpression();
		if (ruleExp != null) {
			context.put(Constants.CURRENT_VALUE, value);
			if (!(Boolean) ruleExp.execute(context))
				throw new ValidateException(
						StringUtils.defaultIfBlank(bmo.getSrcField(), bmo.getEl()) + " value error");
		}
	}

	private Object elExecute(BeanMappingObject bmo, Map<String, Object> context, Object value) {
		Expression exp = bmo.getExpression();
		if (exp != null) {
			context.put(Constants.CURRENT_VALUE, value);
			Object expressionValue = exp.execute(context);
			value = (expressionValue==null?value:expressionValue);
		}
		return value;
	}

	public Class<?> getCurrentClass() {
		return currentClass;
	}

	public void setCurrentClass(Class<?> currentClass) {
		this.currentClass = currentClass;
	}

	public Object getValue(Object o) {
		try {
			return f.get(o);
		} catch (Exception e) {
			throw new ConvertValueException(f.getName() + " get error", e);
		}
	}

	public boolean setValue(Object o, Object value) {
		boolean result = Boolean.FALSE;
		try {
			if (value == null)
				return Boolean.TRUE;
			if (value.getClass() == currentClass)
				f.set(o, value);
			else {
				value = TypeUtils.cast(value, currentClass, null);
				f.set(o, value);
			}
			result = Boolean.TRUE;
		} catch (Exception e) {
			throw new ConvertValueException(f.getName() + " set error", e);
		}
		return result;
	}

	public void isEnum(boolean _isEnum) {
		this._isEnum = _isEnum;
	}

	public void isArray(boolean _isArray) {
		this._isArray = _isArray;
	}

	public void isColltion(boolean _isColltion) {
		this._isColltion = _isColltion;
	}

	public void isCommon(boolean _isCommon) {
		this._isCommon = _isCommon;
	}

	public void isObject(boolean _isObject) {
		this._isObject = _isObject;
	}

	public void isMap(boolean _isMap) {
		this._isMap = _isMap;
	}

	public boolean isArray() {
		return _isArray;
	}

	public boolean isColltion() {
		return _isColltion;
	}

	public boolean isCommon() {
		return _isCommon;
	}

	public boolean isObject() {
		return _isObject;
	}

	public boolean isMap() {
		return _isMap;
	}

	public boolean isRoot() {
		return _isRoot;
	}

	public TreeNode getParent() {
		return parentNode;
	}

	public TreeNode setParent(TreeNode parentNode) {
		this.parentNode = parentNode;
		this.parentNodeName = parentNode.getNodeName();
		return this;
	}

	public String getCurrentNodeName() {
		return f.getName();
	}

	public Class<?> getCurrentNodeClass() {
		return _isRoot ? rootClass : f.getType();
	}

	public String getNodeName() {
		return nodeName;
	}

	public void setNodeName(String nodeName) {
		this.nodeName = nodeName;
	}

	public String getTypeName() {
		return typeName;
	}

	public Object getRootObject() {
		try {
			return _isRoot ? rootClass.newInstance() : null;
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public Field getF() {
		// TODO Auto-generated method stub
		return f;
	}

	@Override
	public Object genObject(JsonNode jn, Object targetObject,Object currentObject, Map<String, Object> context)
			throws InstantiationException, IllegalAccessException {
		Object o = null;
		if (_isRoot){
			o = targetObject;
		}else if(currentObject!=null){
			o = currentObject;
		}else{
			o = f.getType().newInstance();
		}
		return o;
	}

	@Override
	public void bulidMappingNode(String path, BeanMappingObject bmo) {
	}

	@Override
	public String toString() {
		return " [ nodeName=" + nodeName + " typeName=" + typeName + " parentNode=" + parentNodeName + "]";
	}
}
